import { ChatUserCategory } from '@/constants';
import _ from 'lodash';
import { FilterXSS } from 'xss';
import TurndownService from 'turndown';
import { gfm } from '@guyplusplus/turndown-plugin-gfm';
import Cookies from 'js-cookie';

const FILE_TYPE_ICON_MAP = {
    'doc': 'file_icon_word_light',
    'docx': 'file_icon_word_light',
    'md': 'file_icon_markdown_light',
    'csv': 'file_icon_excel_light',
    'txt': 'file_icon_text_light',
    'xls': 'file_icon_excel_light',
    'xlsx': 'file_icon_excel_light',
    'ppt': 'file_icon_ppt_light',
    'pptx': 'file_icon_ppt_light',
    'wps': 'file_icon_ppt_light',
    'ppsx': 'file_icon_ppt_light',
    'pdf': 'file_icon_pdf_light',
    'png': 'file_icon_picture_light',
    'jpg': 'file_icon_picture_light',
    'jpeg': 'file_icon_picture_light',
    'tiff': 'file_icon_picture_light',
    'bmp': 'file_icon_picture_light',
    'gif': 'file_icon_picture_light',
    'webp': 'file_icon_picture_light',
    'heif': 'file_icon_picture_light',
    'heic': 'file_icon_picture_light',
    'jp2': 'file_icon_picture_light',
    'eps': 'file_icon_picture_light',
    'icons': 'file_icon_picture_light',
    'im': 'file_icon_picture_light',
    'pcx': 'file_icon_picture_light',
    'ppm': 'file_icon_picture_light',
    'xbm': 'file_icon_picture_light',
    'html': 'file_icon_web_light'
};

export const wordCounter = (content) => {
    // 去除content中的html标签，转成纯文本。把其中的图片记录到imgurls数组中，并且在文本中相应的位置用IMG_URL_PREFIX替换。
    const imgUrls = [];
    const question = content.replace(/<img[^>]*>/g, (match) => {
        imgUrls.push(match);
        return 'IMG_URL_PREFIX';
    });
    const questionText = question.replace(/<br>/g, 'b').replace(/<[^>]*>/g, '');
    // 计算questionText的字数，其中每个图片的长度为1332
    const imgCount = imgUrls.length;
    const imgLength = imgCount * 1332;
    const tempTxtLength = questionText.replace(/IMG_URL_PREFIX/g, '').length;
    const wordCount = tempTxtLength + imgLength;
    return wordCount || 0;
};

export const subStrWordCount = (content, limit) => {
    const IMG_URL_PREFIX = 'IMG_URL_PREFIX'; // 假设这是你的标识位
    const PREFIX_LENGTH = 1332; // 标识位的字符长度
    // 去除content中的html标签，转成纯文本。把其中的图片记录到imgurls数组中，并且在文本中相应的位置用IMG_URL_PREFIX替换。
    const imgUrls = [];
    const question = content.replace(/<img[^>]*>/g, (match) => {
        imgUrls.push(match);
        return IMG_URL_PREFIX;
    });
    const questionText = question.replace(/<[^>]*>/g, '');

    let result = '';
    let length = 0;

    const parts = questionText.split(IMG_URL_PREFIX);
    for (let i = 0; i < parts.length; i++) {
        const part = parts[i];
        if (i > 0) { // 如果不是第一个部分，那么前面一定有一个标识位
            if (length + PREFIX_LENGTH > limit) {
                break; // 如果加上标识位的长度就超过限制，那么就停止
            }
            result += IMG_URL_PREFIX;
            length += PREFIX_LENGTH;
        }

        if (length + part.length > limit) {
            result += part.substring(0, limit - length); // 如果这个部分的长度会超过限制，那么就截断它
            break;
        } else {
            result += part;
            length += part.length;
        }
    }
    return result;
};

export const extractTextFromHtml = (htmlString) => {
    var tempDiv = document.createElement('div');
    tempDiv.innerHTML = htmlString;
    return tempDiv.textContent || tempDiv.innerText || '';
};

/**
 * 生成UUID
 * @returns {string} UUID，形式如 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx，其中x的范围为0~9a-f
 */
export const getUUID = () => {
    function S4 () {
        return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    }
    return `${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`;
};

export const buildMap = (data) => {
    const sMap = {};

    data.map((r) => {
        sMap[String(r.value)] = r.text;
    });

    return sMap;
};

export const isEmpty = (value) => {
    // 检查 null 和 undefined
    if (value == null) {
        return true;
    }

    // 检查空字符串
    if (typeof value === 'string' && value.trim() === '') {
        return true;
    }

    // 检查空对象或空数组
    if (typeof value === 'object' && Object.keys(value).length === 0) {
        return true;
    }

    // 如果都不是，则返回 false
    return false;
};

export const isJSON = (data) => {
    try {
        JSON.parse(data);
        return true;
    } catch (error) {
        return false;
    }
};
/**
 * 获取 location hash 参数
 * @param {string} variable 参数名
 * @returns {string} 参数值
 */
export const getIframeQueryVariable = (variable) => {
    const query = window.location.search.substring(1);

    const vars = query.split('&');
    for (let i = 0; i < vars.length; i++) {
        const pair = vars[i].split('=');
        if (pair[0] === variable) {
            return pair[1];
        }
    }
    return '';
};
/**
 * 获取 location hash 参数
 * @param {string} variable 参数名
 * @returns {string} 参数值
 */
export const getQueryVariable = (variable) => {
    
    const query = window.location.hash.split('?');
    
    if (query.length < 2) {
        return '';
    }

    const vars = query[1].split('&');

    // eslint-disable-next-line @typescript-eslint/prefer-for-of
    for (let i = 0; i < vars.length; i++) {
        const pair = vars[i].split('=');
        if (pair[0] === variable) {
            console.log(pair[1],'getQueryVariablegetQueryVariable')
            return decodeURI(pair[1]);
        }
    }
    return '';
    return 'single';
};

export const getShareCode = (params) => {
    const hash = window.location.hash;
    const regex = /\/chat\/([^/?]+)/;
    const match = hash.match(regex);
    return (match && match[1]) || getQueryVariable('shareCode');
};

/**
 * 滚动至指定dom底部
 */
export const scrollToBottom = (sDom, sTop) => {
    if (!sDom) return;

    sDom.scrollTo({
        top: sTop
        // behavior: 'smooth'
    });
};

// 防抖
let timer = null;
export const debounceOne = (func, wait) => {
    return (...args) => {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            func(...args);
        }, wait);
    };
};
/**
 * 防抖函数
 * @param method 事件触发的操作
 * @param delay 触发之后多长时间后执行
 * @returns {Function}
 */
export const debounce = (method, delay) => {
    let timer = null;
    return function () {
        let self = this;
        let args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function () {
            method.apply(self, args);
        }, delay);
    };
};

/**
 * 是否移动端
 */
export const isMobile = () => {
    const matchMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
    if (matchMobile) return !!matchMobile.length;
    return false;
};

/**
 * 数组去重
 */
export const arrayUnique = (arr, replaceKey, holdKey) => {
    let temp = {};

    return arr.reduce((prev, cur) => {
        if (!temp[cur[replaceKey]]) {
            temp[cur[replaceKey]] = {index: prev.length};
            prev.push(cur);
        } else {
            const oldItem = temp[cur[replaceKey]];
            cur[holdKey] = oldItem[holdKey];
            prev.splice(oldItem['index'], 1, cur);
        }

        return prev;
    }, []);
};

export const generateRequestId = (length = 10) => {
    const data =
        ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
            'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
            'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
            's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
    let nums = '';
    for (let i = 0; i < length; i++) {
        const r = parseInt(Math.random() * 61, 10);
        nums += data[r];
    }
    return nums + '-' + parseInt(Math.random() * 10000000000, 10);
};

export const getLastNum = (num) => {
    return String(num)[String(num).length - 1];
};

export const xssHtmlOptions = {
    css: false,
    // whiteList: {
    // ...whiteList,
    // a: ['class', 'style', 'target', 'data-link-url', 'rel', 'href', 'data-link-url'],
    img: ['class', 'style', 'alt'],
    // audio: ['class', 'style', 'controls', 'controlsList', 'src'],
    // video: ['class', 'style', 'controls', 'width', 'height', 'webkit-playsinline', 'playsinline', 'x5-playsinline', 'poster', 'src'],
    // p: ['class', 'style'],
    // strong: ['class', 'style'],
    // span: ['contenteditable'],
    // div: ['class', 'style', ''],
    // },
    // 文档地址：https://www.npmjs.com/package/xss
    onTagAttr (tag, name, value, isWhiteAttr) {
        if (tag.indexOf('_') !== -1) {
            // 如果包含下划线，则认为是自定义标签，放过
            return `${name}="${value}"`;
        }
        const tagIgnore = ['div', 'span', 'strong', 'p', 'mark'];
        if (tagIgnore.includes(tag)) {
            // do not filter its attributes
            const blockAttrsReg = /^on/;
            if (!blockAttrsReg.test(name)) {
                return `${name}="${value}"`;
            }
        }
        const tagAttrIgnore = ['class', 'style'];
        if (tagAttrIgnore.includes(name)) {
            return `${name}="${value}"`;
        }
        // 自定义属性省略
        const aTagAttrIgnore = ['rel', 'data-link-url'];
        if (tag === 'a' && aTagAttrIgnore.includes(name)) {
            return `${name}="${value}"`;
        }
        const videoTagAttrIgnore = [
            'rel',
            'webkit-playsinline',
            'x5-playsinline',
            'title',
            'size',
            'format'
        ];
        if (tag === 'video' && videoTagAttrIgnore.includes(name)) {
            return `${name}="${value}"`;
        }
        const audioTagAttrIgnore = [
            'controls',
            'controlslist',
            'id',
            'speed',
            'title',
            'size',
            'format',
            'data-duration-ms'
        ];
        if (tag === 'audio' && audioTagAttrIgnore.includes(name)) {
            return `${name}="${value}"`;
        }
        const imgTagAttrIgnore = ['alt', 'data-value'];
        if (tag === 'img' && imgTagAttrIgnore.includes(name)) {
            return `${name}="${value}"`;
        }
        // if (!isWhiteAttr) {
        //   return '';
        // }
    }
};
export const xssHandlerProcess = (value) => {
    const xssHandler = new FilterXSS(xssHtmlOptions);
    return xssHandler.process(value);
};
export const imgStyle = (num) => {
    let color = [
        '#1492FF', '#1492FF',
        '#0FB87F', '#0FB87F',
        '#FF8345', '#FF8345',
        '#6A45E5', '#6A45E5',
        '#4A70FF', '#4A70FF'
    ];
    let border = [
        '#D0EFFF', '#D0EFFF',
        '#C0EBD7', '#C0EBD7',
        '#FFEBDA', '#FFEBDA',
        '#E2D5F8', '#E2D5F8',
        '#DBE8FF', '#DBE8FF'
    ];
    let background = [
        '#ECF9FF', '#ECF9FF',
        '#E6F8EF', '#E6F8EF',
        '#FFF8F0', '#FFF8F0',
        '#F4EEFC', '#F4EEFC',
        '#F1F6FF', '#F1F6FF'
    ];
    let index = getLastNum(num);
    return {
        color: color[index],
        border: `0.5px solid ${border[index]}`,
        background: background[index]
    };
};

function escapeHtml (str) {
    return str.replace(/[&<>"'/]/g, function (match) {
        return {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#39;',
            '/': '&#x2F;'
        }[match];
    });
}

export const escapeUserInput = (input) => {
    const parts = input.split(/(<script[^>]*>.*?<\/script>|<[^>]*>)/gi);
    return parts.map(part => {
        if (part.startsWith('<script')) {
            return escapeHtml(part);
        } else if (part.startsWith('<')) {
            return escapeHtml(part);
        } else {
            return part;
        }
    }).join('');
};

// let mdStr = "Hello ~ World ~~ Test ~~~";
// console.log(escapeSingleTilde(mdStr));  // 输出: "Hello \~ World ~~ Test ~~~"
export const escapeSingleTilde = (str) => {
    return str.replace(/(^|[^~\\])~([^~]|$)/g, '$1\\~$2');
};

// 如果markdown字符串里的*号如果前后有数字，就在前面加上转义符
export const escapeMarkdownStars = (str) => {
    return str.replace(/([\d+\-/=≈])\*/g, '$1\\*');
};

const matchImageLinks = (text) => {
    const regex = /!\[([^\]]+)\]\(([^)]+)\)/g;
    const matches = [];
    let match;
    while ((match = regex.exec(text)) !== null) {
        matches.push(match[0]);
    }
    return matches;
};

export const replaceLinks = (text) => {
    // console.log('【workflow tokens】replaceLinks', text);
    if (!text) return text;
    text = text.replace(/\\([*_()])/g, '$1');
    // text = text.replace(/(?<!<[^>]*)>/g, '\\>');
    // 先把里面的图片保留
    const matchImgs = matchImageLinks(text);
    matchImgs.forEach((i, index) => {
        text = text.replace(i, `IMG_URL${index}`);
    });
    const from = getQueryVariable('from');
    const replaceUrl = from ? '' : `${window.location.protocol}//${window.location.host}/lke#/link-info/redirect?target=`;
    const regex = new RegExp(
        '(\\[([^\\]]+)\\]\\((https?:\\/\\/(?!oaqbot\\.qidian\\.qq\\.com)[^\\s)]+)\\))|(<a\\s+[^>]*href="((?!oaqbot\\.qidian\\.qq\\.com)[^"]+)"[^>]*>(.*?)<\\/a>)',
        'gi'
    );
    text = text.replace(regex, (match, p1, p2, p3, p4, p5, p6) => {
        if (p1 && p2) {
            return `[${p2}](${replaceUrl}${p3})`;
        } else if (p5 && !p5.startsWith('#')) {
            return `<a href="${replaceUrl}${p5}">${p6}</a>`;
        } else {
            return match;
        }
    });
    matchImgs.forEach((i, index) => {
        text = text.replace(`IMG_URL${index}`, i);
    });

    text = escapeSingleTilde(text);

    text = escapeMarkdownStars(text);

    return xssHandlerProcess(text);
};

// 把string变为大写驼峰
export const capitalizedCamelCase = (value) => {
    return _.upperFirst(_.camelCase(value));
};

// 把数据中所有key变为snake_case
export const convertToSnakeCase = (obj) => {
    if (Array.isArray(obj)) {
        return obj.map(v => convertToSnakeCase(v));
    } else if (obj && obj.constructor === Object) {
        let res = {};
        Object.entries(obj).forEach(([key, value]) => {
            res[_.snakeCase(key)] = convertToSnakeCase(value);
        });
        return res;
    } else {
        return obj;
    }
};

// 把数据中所有key和value变为大写驼峰
export const convertToCapitalizedCamelCase = (obj) => {
    if (Array.isArray(obj)) {
        return obj.map(v => convertToCapitalizedCamelCase(v));
    } else if (obj && obj.constructor === Object) {
        let res = {};
        Object.entries(obj).forEach(([key, value]) => {
            res[capitalizedCamelCase(key)] = convertToCapitalizedCamelCase(value);
        });
        return res;
    } else {
        return obj;
    }
};

// 图片分辨率
export const checkImageResolution = (file, maxSize = 2000) =>
    new Promise((resolve, reject) => {
        const image = new Image();
        image.onload = function () {
            if (image.width > maxSize || image.height > maxSize) {
                reject(new Error('图片尺寸超出限制'));
            }
            resolve(true);
        };
        image.src = URL.createObjectURL(file);
    });

// const FILE_TYPE_ICON_MAP1 = {
//     'doc': 'file_icon_word_light',
//     'docx': 'file_icon_word_light',
//     'md': 'file_icon_markdown_light',
//     'csv': 'file_icon_excel_light',
//     'txt': 'file_icon_text_light',
//     'xls': 'file_icon_excel_light',
//     'xlsx': 'file_icon_excel_light',
//     'ppt': 'file_icon_ppt_light',
//     'pptx': 'file_icon_ppt_light',
//     'pdf': 'file_icon_pdf_light'
// };

export const getFileIcon = (fileType) => {
    const defaultIconName = 'file_icon_text_light';
    return FILE_TYPE_ICON_MAP[fileType] || defaultIconName;
};

export const getFileIconUrl = (fileType) => {
    const defaultIconName = 'file_icon_text_light';
    const svgType = FILE_TYPE_ICON_MAP[fileType] || defaultIconName;
    return `https://cdn.xiaowei.qq.com/static/lke/icon/${svgType}.svg`;
};

export const formateSize = (size) => {
    if (typeof size === 'string') {
        size = parseInt(size, 10);
    }
    let tempSize = size + 'B';
    if (size > 1024 * 1024) {
        tempSize = (size / 1024 / 1024).toFixed(2) + 'MB';
    } else if (size > 1024) {
        tempSize = (size / 1024).toFixed(2) + 'KB';
    }
    return tempSize;
};

export const fileString = (str, len, type) => {
    if (!str) {
        return '';
    }
    if (str.length <= 6) {
        return `${str}.${type}`;
    } else {
        let count = 0;
        let result = '';
        for (let i = 0; i < str.length; i++) {
            let char = str[i];
            // eslint-disable-next-line no-control-regex
            count += /[^\x00-\xff]/.test(char) ? 1 : 0.5;
            if (count > len) {
                break;
            }
            result += char;
        }
        return `${result}....${type}`;
    }
};

/**
 * 不同域名使用不同的防水墙验证码AppId
 */
export const getCaptchaAppId = () => {
    return '197595422';
};

/**
 * 不同用户端域名，使用不同的账号体系
 * 腾讯云账号：依赖腾讯云登录态
 * 手机号登录：依赖手机登录后的登录态
 * @returns
 */
export const getUserCategory = () => {
    const { pathname } = window.location;
    if (pathname.includes('/webim_exp')) {
        return ChatUserCategory.PHONE_NUMBER;
    }
    return ChatUserCategory.TENCENT_CLOUD;
};

export const html2Markdown = (htmlData) => {
    const tDown = new TurndownService();
    tDown.use(gfm); // tDown.use([tables, strikethrough]);

    // 自定义配置（rule名不能重复）。这里指定 `pre` 标签为代码块，前后加换行，防止显示异常
    tDown.addRule('pre2Code', {
        filter: ['pre'],
        replacement (content) {
            return '```\n' + content + '\n```';
        }
    });

    const mdData = tDown.turndown(htmlData);

    // 转化不支持的图片链接格式
    // 正则表达式匹配 Markdown 图片链接
    const regex = /\[(!\[\]\((.*?)\))\]\((.*?)\)/g;

    // 使用空字符串替换匹配到的链接，只保留图片部分
    const result = mdData.replace(regex, '$1');

    return result;
};

export const getClarifyQuestion = ({ content: markdownText = '', reply_method }) => {
    if (reply_method !== 15) return {};
    const pattern = /\[.*?\]\(\)/;

    // 查找第一个链接的位置
    const match = markdownText.match(pattern);
    let content = '';

    if (match) {
    // 获取第一个链接之前的内容
        content = markdownText.slice(0, match.index);
    } else {
        console.log('没有找到链接');
        content = markdownText;
    }

    const reg = new RegExp(/\[([^\]]+)\]\(\)/g);
    let matches;
    const results = [];
    while ((matches = reg.exec(markdownText)) !== null) {
        results.push(matches[1]); // matches[1] 是链接文本
    }
    return {
        content,
        clarifyResults: results
    };
};

export const formattedEllipsis = (text, length = 40) => {
    if (Object.prototype.toString.call(text) !== '[object String]') { return; };
    if (text && text.length > length) {
        return text.slice(0, length).slice(0, -3) + '...';
    }
    return text;
};

/**
 * 移除Markdown格式的标题和列表标记
 * @param {string} text - 输入的文本
 * @return {string} 处理后的文本
 */
function removeMarkdownFormatting (text) {
    // 移除标题 (#, ##, ###, 等)
    text = text.replace(/^#+\s/gm, '');

    // 移除无序列表标记 (-, *, +)
    text = text.replace(/^[-*+]\s/gm, '');

    // 移除有序列表标记 (1., 2., 等)
    text = text.replace(/^\d+\.\s/gm, '');
    // 移除加粗(**) 斜体文本：*斜体*
    const regex = new RegExp(
        '(\\*\\*|__)(?=\\S)(.+?[*_]*)(?<=\\S)\\1|\\*(?=\\S)(.*?)(?<=\\S)\\*',
        'g'
    );
    text = text.replace(regex, '$2$3');
    return text;
}

/**
 * 移除图片和视频标签
 * @param {string} text - 输入的文本
 * @return {string} 处理后的文本
 */
function removeMediaTags (text) {
    // 移除Markdown格式的图片
    text = text.replace(/!\[.*?\]\(.*?\)/g, '');

    // 移除HTML视频标签
    const regex = new RegExp('<video\\b[^>]*>([\\s\\S]*?<\\/video>)?', 'gi');
    text = text.replace(regex, '');

    return text;
}

/**
 * 处理Markdown格式的链接
 * @param {string} text - 输入的文本
 * @return {string} 处理后的文本,链接被转换为纯文本URL
 */
function processLinks (text) {
    return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)');
}

/**
 * 保留LaTeX公式
 * @param {string} text - 输入的文本
 * @return {string} 处理后的文本,LaTeX公式保持原样
 */
function preserveLatex (text) {
    // 这个函数不需要对文本做任何改变
    // LaTeX公式会被其他函数保留下来
    return text;
}

function removeStopHTMLElement (text) {
    return text.replace(/<span class="stop-ws">\| 已停止生成<\/span>/g, '');
}

/**
 * 去除 标签
 * @param {*} html
 * @returns
 */
function stripTags (html) {
    // 创建一个临时的div元素
    let temp = document.createElement('div');

    // 将HTML字符串设置为div的内容
    temp.innerHTML = html;

    // 获取所有文本内容，并去除前后空白
    return temp.textContent || temp.innerText || '';
}

/**
 * 主函数：处理并复制文本
 * @param {string} content - 后端返回的原始内容
 * @return {string} 处理后可以直接复制的文本
 */
export function processAndCopyContent (content) {
    let processedContent = content;

    // 应用所有处理函数
    processedContent = removeMarkdownFormatting(processedContent);
    processedContent = removeMediaTags(processedContent);
    processedContent = processLinks(processedContent);
    processedContent = preserveLatex(processedContent);
    processedContent = removeStopHTMLElement(processedContent);
    processedContent = stripTags(processedContent);

    return processedContent;
}

export const getCookie = (key) => {
    if (key === 'bot_biz_id') {
        if (window.microAppProps) {
            const { botBizId } = window.microAppProps;
            if (botBizId) {
                return botBizId;
            }
        } else {
            return getIframeQueryVariable('appid') || getQueryVariable('appid') || getQueryVariable('expAppBizId') || getQueryVariable(key) || Cookies.get(key) || localStorage.getItem(key);
        }
    }

    return getQueryVariable(key) || Cookies.get(key) || localStorage.getItem(key);
};

export function getPreviewUrl (item) {
    const botBizId = getCookie('bot_biz_id');
    const from = getIframeQueryVariable('from');
    const token = getIframeQueryVariable('token');
    // 这里添加 page 的信息
    let doc_name = item.doc_name;
    // 使用正则表达式匹配最后一个点后面的内容
    const match = doc_name.match(/\.([^.]+)$/);
    // 如果匹配成功，返回文件格式，否则返回 ''
    let file_type = match ? match[1].toLowerCase() : '';

    // 先考虑 类型 md txt 需要处理
    if (file_type === 'md' || file_type === 'txt') {
        let href = window.location.href.split('#')[0];
        let url = `${href}#/preview/md?id=${item.doc_biz_id}&botBizId=${botBizId}&test=1`;
        if (from && token) {
            url = `${url}&from=${from}&token=${token}`;
        }
        return url;
    } else {
        let tempUrl = `${window.location.origin}/preview?id=${item.doc_biz_id}&botBizId=${botBizId}&test=1`;
        if (from && token) {
            tempUrl = `${tempUrl}&from=${from}&token=${token}`;
        }
        return tempUrl;
    }
};
export const universalStringToBase64 = (str) => {
    if (typeof TextEncoder !== 'undefined') {
        // 现代浏览器
        const encoder = new TextEncoder();
        const bytes = encoder.encode(str);
        return btoa(String.fromCharCode(...bytes));
    } else {
        // 旧浏览器
        return btoa(unescape(encodeURIComponent(str)));
    }
};

// tts 实时消息节流处理,因为 tts 合成后，可以读一段时间，后面的内容可以少处理几次，避免性能问题
// 最开始一秒内的调用按50ms节流，后面按1秒递增节流，最大10秒节流，如果有结束标识就立刻执行
export const ttsThrottled = (fn) => {
    let context = null; // 保存原始上下文
    let pendingArgs = null; // 待处理参数
    let timeoutId = null; // 定时器ID
    let callCount = 0; // 调用计数器
    let lastTime = 0;

    // 核心执行器（保留上下文）
    const execute = () => {
        lastTime = Date.now();
        callCount++;
        if (!context) return;
        fn.apply(context, pendingArgs);
        context = null;
        pendingArgs = null;
    };

    // 动态间隔计算
    const calcInterval = () => {
        const phase = Math.floor(callCount / 10);
        return Math.min(100 + phase * 1000, 10000);
    };

    // 节流控制器
    const throttled = function (...args) {
        // 保存当前 Vue 实例上下文
        context = this;
        pendingArgs = args;

        // 结束标识处理
        if (args[0].is_final) {
            if (timeoutId) clearTimeout(timeoutId);
            execute();
            callCount = 0;
            return;
        }

        // 首次调用立即执行
        if (callCount === 0) {
            execute();
            return;
        }

        const delay = calcInterval();
        if (Date.now() - lastTime > delay) {
            execute();
        }
        if (timeoutId) { clearTimeout(timeoutId); }
        timeoutId = setTimeout(() => {
            execute();
            timeoutId = null;
        }, delay);
    };
    throttled.resetCount = function () {
        callCount = 0;
        const delay = calcInterval();
        if (timeoutId) { clearTimeout(timeoutId); }
        timeoutId = setTimeout(() => {
            execute();
            timeoutId = null;
        }, delay);
    };
    throttled.getCount = function () {
        return callCount;
    };
    return throttled;
};